package com.apobates.jforum.grief.aurl.core;

import com.apobates.jforum.grief.aurl.utils.defend.Assertion;
import com.apobates.jforum.grief.aurl.utils.defend.PredicateQueue;
import com.apobates.jforum.grief.aurl.utils.defend.DefendAssert;
import com.apobates.jforum.grief.aurl.utils.Pair;
import com.apobates.jforum.grief.aurl.parser.PatternParser;
import com.apobates.jforum.grief.aurl.spring.CollectionUtils;
import com.apobates.jforum.grief.aurl.utils.CurlyBraceSymbol;
import com.apobates.jforum.grief.aurl.spring.ObjectUtils;
import com.apobates.jforum.grief.aurl.spring.StringUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * AUrlRawMeta构建器
 * 示例：
 *    AUrlRawMetaBuilder.newInstance().scheme("http").host("www.baeldung.com").path("/junit-5");
 */
public final class AUrlMetaBuilder {
    private String scheme="http";
    private String host="localhost";
    private String port="80";
    private String fragment;
    private String ssp;
    private String userInfo;
    private static final Object[] EMPTY_VALUES = new Object[0];
    private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
    private static final Pattern PATH_VARI = Pattern.compile("\\{(.*?)\\}");
    private ResourceParameter pathBuilder;
    private static final String PATH_SEPARATOR="/";

    private AUrlMetaBuilder(){
        this.pathBuilder = new ResourceParameter();
    }
    public static AUrlMetaBuilder getInstance(){
        return new AUrlMetaBuilder();
    }
    // --------------------------------------->返回方法
    public String getScheme(){
        return this.scheme;
    }
    public String getHost(){
        return this.host;
    }
    public String getPort(){
        return this.port;
    }
    public String getFragment(){
        return this.fragment;
    }
    public String getSsp(){
        return this.ssp;
    }
    public String getUserInfo(){
        return this.userInfo;
    }
    public String getPath(){
        return this.pathBuilder.getPath();
    }
    public List<String> getPathSegments(){
        return this.pathBuilder.getPathSegments();
    }
    public Map<String, List<String>> getQueryParams(){
        return this.pathBuilder.getParameters();
    }
    // --------------------------------------->构建方法

    /**
     * 设置协议模式名称, 默认为:http. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param scheme 模式名称. 例: https
     * @return
     */
    public AUrlMetaBuilder scheme(String scheme) {
        this.scheme = scheme;
        return this;
    }

    /**
     * 设置协议模式名称
     * @param scheme scheme 模式名称. 例: https
     * @return
     */
    public AUrlMetaBuilder scheme(Optional<String> scheme) {
        if(scheme.isPresent()){
            this.scheme = scheme.get();
        }
        return this;
    }

    /**
     * 设置主机名称, 默认为:localhost. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param host 主机名称. 例: www.a.com
     * @return
     */
    public AUrlMetaBuilder host(String host) {
        this.host = host;
        if (host != null) {
            resetSchemeSpecificPart();
        }
        return this;
    }

    /**
     * 设置主机名称, 默认为:localhost.
     * @param host 主机名称. 例: www.a.com
     * @return
     */
    public AUrlMetaBuilder host(Optional<String> host) {
        if(host.isPresent()){
            this.host = host.get();
            if (host != null) {
                resetSchemeSpecificPart();
            }
        }
        return this;
    }

    /**
     * 设置端口号. 默认为:80
     * @param port 端口号
     * @return
     */
    public AUrlMetaBuilder port(int port) {
        this.port = String.valueOf(port);
        if (port > -1) {
            resetSchemeSpecificPart();
        }
        return this;
    }

    /**
     * 设置端口号. 默认为:80
     * @param port 端口号
     * @return
     */
    public AUrlMetaBuilder port(String port) {
        this.port = port;
        if (port != null) {
            resetSchemeSpecificPart();
        }
        return this;
    }

    /**
     * 设置端口号. 默认为:80
     * @param port 端口号
     * @return
     */
    public AUrlMetaBuilder port(Optional<String> port) {
        if(port.isPresent()){
            this.port = port.get();
            if (port != null) {
                resetSchemeSpecificPart();
            }
        }
        return this;
    }

    /**
     * 设置路径. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param path 路径字符串. 例: /order/create
     * @return
     */
    public AUrlMetaBuilder path(String path) {
        this.pathBuilder.path(path);
        resetSchemeSpecificPart();
        return this;
    }

    /**
     * 设置路径.
     * 注意: 方法与pathSegment不能混合使用
     * @param path 路径字符串
     * @return
     */
    public AUrlMetaBuilder path(Optional<String> path) {
        if(path.isPresent()){
            this.pathBuilder.path(path.get());
            resetSchemeSpecificPart();
        }
        return this;
    }

    /**
     * 设置路径. 例: http://www.a.com:8080/order/create?product=100#descript
     * 注意: 方法与path不能混合使用
     * @param pathSegments 路径片段数组. 例: order,create
     * @return
     * @throws IllegalArgumentException
     */
    public AUrlMetaBuilder pathSegment(String... pathSegments) throws IllegalArgumentException {
        this.pathBuilder.pathSegments(pathSegments);
        resetSchemeSpecificPart();
        return this;
    }

    /**
     * 设置查询参数字符串. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param query 查询参数字符串. 例: product=100
     * @return
     */
    public AUrlMetaBuilder query(String query) {
        if (query != null) {
            Matcher matcher = QUERY_PARAM_PATTERN.matcher(query);
            while (matcher.find()) {
                String name = matcher.group(1);
                String eq = matcher.group(2);
                String value = matcher.group(3);
                queryParam(name, (value != null ? value : (StringUtils.hasLength(eq) ? "" : null)));
            }
            resetSchemeSpecificPart();
        }
        else {
            this.pathBuilder.paramClear();
        }
        return this;
    }

    /**
     * 设置查询参数字符串.
     * @param query 查询参数字符串
     * @return
     */
    public AUrlMetaBuilder query(Optional<String> query) {
        if(query.isPresent()){
            String temp = query.get();
            this.query(temp);
        }
        return this;
    }

    /**
     * 设置查询参数. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param name 参数名称. 例: product
     * @param values 参数值集合
     * @return
     */
    public AUrlMetaBuilder queryParam(String name, Collection<?> values) {
        return queryParam(name, (CollectionUtils.isEmpty(values) ? EMPTY_VALUES : values.toArray()));
    }

    /**
     * 设置查询参数. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param name 参数名称. 例: product
     * @param values 参数值列表
     * @return
     */
    public AUrlMetaBuilder queryParam(String name, Object... values) {
        if (!ObjectUtils.isEmpty(values)) {
            for (Object value : values) {
                String valueAsString = ObjectUtils.getQueryParamValue(value);
                this.pathBuilder.param(name, valueAsString);
            }
        }
        else {
            this.pathBuilder.param(name, null);
        }
        resetSchemeSpecificPart();
        return this;
    }

    /**
     * 设置片段. 例: http://www.a.com:8080/order/create?product=100#descript
     * @param fragment 片段名称. 例: descript
     * @return
     */
    public AUrlMetaBuilder fragment(String fragment) {
        if (fragment != null) {
            this.fragment = fragment;
        }
        return this;
    }

    public AUrlMetaBuilder fragment(Optional<String> fragment) {
        if(fragment.isPresent()){
            String temp = fragment.get();
            this.fragment = temp;
        }
        return this;
    }

    /**
     * Set the URI scheme-specific-part. When invoked, this method overwrites
     * {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
     * {@linkplain #port(int) port}, {@linkplain #path(String) path}, and
     * {link query(String) query}.
     * @param ssp the URI scheme-specific-part, may contain URI template parameters
     * @return this UriComponentsBuilder
     */
    public AUrlMetaBuilder schemeSpecificPart(String ssp) {
        this.ssp = ssp;
        resetHierarchicalComponents();
        return this;
    }

    /**
     * 设置用户信息. 例: https://Arnab_Kundu@www.a.com
     * @param userInfo 用户信息, 例: Arnab_Kundu
     * @return
     */
    public AUrlMetaBuilder userInfo(String userInfo) {
        this.userInfo = userInfo;
        resetSchemeSpecificPart();
        return this;
    }

    public AUrlMetaBuilder userInfo(Optional<String> userInfo) {
        if(userInfo.isPresent()) {
            this.userInfo = userInfo.get();
            resetSchemeSpecificPart();
        }
        return this;
    }
    /**
     * Create a builder that is initialized with the given URI string.
     * <p><strong>Note:</strong> The presence of reserved characters can prevent
     * correct parsing of the URI string. For example if a query parameter
     * contains {@code '='} or {@code '&'} characters, the query string cannot
     * be parsed unambiguously. Such values should be substituted for URI
     * variables to enable correct parsing:
     * <pre class="code">
     * String uriString = &quot;/hotels/42?filter={value}&quot;;
     * UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;);
     * </pre>
     * @param uri the URI string to initialize with
     * @return the new {@code UriComponentsBuilder}
     */
    public static AUrlMetaBuilder fromUriString(String uri) throws IllegalArgumentException {
        if(StringUtils.hasLength(uri)) {
            return new PatternParser().parse(uri);
        }
        return AUrlMetaBuilder.getInstance();
    }

    private void resetHierarchicalComponents() {
        this.userInfo = null;
        this.host = null;
        this.port = null;
        this.pathBuilder = new ResourceParameter();
    }

    private void resetSchemeSpecificPart() {
        this.ssp = null;
    }

    /**
     * 构建AUrlMeta实例
     * @return
     * @throws IllegalStateException 未进行值设置后调用抛出
     */
    public AUrlMeta build(){
        return new AUrlMeta(this);
    }

    /**
     * 构建AUrlMeta实例
     * 只适用于: 路径中有占位符的情况. 若无占位符方法会抛出异常
     * @param paramValueMap key=路径中变量的占位符, value=占位符的替换值
     * @return
     */
    public AUrlMeta templateByMap(Map<String,String> paramValueMap){
        CurlyBraceSymbol cbs = expectCBS(PredicateQueue.require().notEmpty().asserts(Assertion.single(paramValueMap))); // (DefendAssert.single(paramValueMap).notNull().notEmpty());
        return new AUrlMeta(AUrlMetaBuilder
                                .getInstance()
                                .scheme(this.scheme)
                                .host(this.host)
                                .port(this.port)
                                .userInfo(this.userInfo)
                                .path(cbs.replace(paramValueMap))
                                .query(this.pathBuilder.getQuery())
                                .fragment(this.fragment));
    }

    /**
     * 构建AUrlMeta实例
     * 只适用于: 路径中有占位符的情况. 若无占位符方法会抛出异常
     * @param pairset Pair的集合. left=占位符, right=占位符的替换值
     * @return
     */
    public AUrlMeta template(Collection<Pair<String,String>> pairset){
        CurlyBraceSymbol cbs = expectCBS(PredicateQueue.require().notEmpty().asserts(Assertion.single(pairset))); // (DefendAssert.single(pairset).notNull().notEmpty());
        return new AUrlMeta(AUrlMetaBuilder
                .getInstance()
                .scheme(this.scheme)
                .host(this.host)
                .port(this.port)
                .userInfo(this.userInfo)
                .path(cbs.replace(pairset))
                .query(this.pathBuilder.getQuery())
                .fragment(this.fragment));
    }

    /**
     * 构建AUrlMeta实例
     * 只适用于: 路径中有占位符的情况. 若无占位符方法会抛出异常
     * @param instance javaBean实例。要求:占位符可以通过getter方法取得值
     * @return
     */
    public <T> AUrlMeta templateByObj(T instance){
        CurlyBraceSymbol cbs = expectCBS(PredicateQueue.require().asserts(Assertion.single(instance))); // (DefendAssert.single(instance).notNull());
        return new AUrlMeta(AUrlMetaBuilder
                .getInstance()
                .scheme(this.scheme)
                .host(this.host)
                .port(this.port)
                .userInfo(this.userInfo)
                .path(cbs.replace(instance))
                .query(this.pathBuilder.getQuery())
                .fragment(this.fragment));
    }

    private <T> CurlyBraceSymbol expectCBS(DefendAssert<T> other){
        return PredicateQueue.<String>predicate(StringUtils::hasLength, "路径暂时不可用")
                .asserts(Assertion.single(this.pathBuilder.getPath()))
                .before(other)
                .get()
                .map(CurlyBraceSymbol::new)
                .orElseGet(CurlyBraceSymbol::empty);
    }

    /**
     * 返回字符串值
     * @return
     */
    @Override
    public String toString(){
        return this.build().toString();
    }

    /**
     * 是否未设置过主机信息
     * @return 是返回true
     */
    private boolean isEmptyHost(){
        return null == getHost() || getHost().equals("localhost");
    }
    // SEE: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL
    private static class ResourceParameter{
        private String path;
        private Map<String,List<String>> parameters = new LinkedHashMap<>();

        private ResourceParameter(){
        }
        public ResourceParameter path(String path){
            if(StringUtils.hasLength(path) && null == this.path) {
                this.path = path;
            }
            return this;
        }
        public ResourceParameter pathSegments(String... segments){
            if(segments.length>0 && null == this.path) {
                this.path = String.join(PATH_SEPARATOR, segments);
            }
            return this;
        }
        public ResourceParameter paramClear(){
            this.parameters.clear();
            return this;
        }
        public ResourceParameter param(String name, String value){
            if(!this.parameters.containsKey(name)){
                this.parameters.put(name, new ArrayList<>());
            }
            if (StringUtils.hasLength(value)) {
                this.parameters.get(name).add(value);
            }
            return this;
        }
        public String getPath(){
            return this.path;
        }
        public String getQuery(){
            List<String> data = new ArrayList<>(parameters.size());
            for (Map.Entry<String,List<String>> entry: parameters.entrySet()) {
                data.add(String.format("%s=%s", entry.getKey(), String.join(",", entry.getValue())));
            }
            return String.join("&", data);
        }
        public Map<String,List<String>> getParameters(){
            return new HashMap<>(this.parameters);
        }
        public List<String> getPathSegments(){
            if(null == path) {
                return Collections.emptyList();
            }
            String tmpPath = this.path.startsWith(PATH_SEPARATOR)?this.path.substring(1):this.path;
            return List.of(tmpPath.split(PATH_SEPARATOR));
        }

        /**
         * 是否设置过值
         * @return 未设置返回true
         */
        public boolean isEmpty(){
            return null == this.path && parameters.isEmpty();
        }
    }
}
